#include <stdlib.h>

#include "list.h"

/*
 * Creating things
 */

struct lnode *make_lnode(struct lnode *next, struct lnode *prev, void *data) {
	struct lnode *n = malloc(sizeof(struct lnode));
	n->next = next;
	n->prev = prev;
	n->data = data;
	return n;
}

list_t *make_list() {
	list_t *l = malloc(sizeof(list_t));
	l->head = NULL;
	l->tail = NULL;
	l->size = 0;
	return l;
}


/*
 * Adding to lists
 */

void append_list(list_t *list, void *data) {
	if (list->size == 0) {
		struct lnode *nhead = make_lnode(NULL, NULL, data);
		list->head = nhead;
		list->tail = nhead;
		list->size++;
		return;
	}
	struct lnode *tail = list->tail;
	struct lnode *ntail = make_lnode(NULL, tail, data);
	tail->next = ntail;
	list->tail = ntail;
	list->size++;
}

void prepend_list(list_t *list, void *data) {
	if (list->size == 0) {
		struct lnode *nhead = make_lnode(NULL, NULL, data);
		list->head = nhead;
		list->tail = nhead;
		list->size++;
		return;
	}
	struct lnode *head = list->head;
	struct lnode *nhead = make_lnode(head, NULL, data);
	head->prev = nhead;
	list->head = nhead;
	list->size++;
}

void insert_list(list_t *list, void *data, int loc) {
	struct lnode *ins_pt = list->head;
	int i;
	for (i=0; i<loc; i++)
		ins_pt = ins_pt->next;
	// put the new node before ins_pt
	struct lnode *nnode = make_lnode(ins_pt, ins_pt->prev, data);
	if (ins_pt->prev)
		ins_pt->prev->next = nnode;
	else
		list->head = nnode;
	ins_pt->prev = nnode;
}

/*
 * Getting list contents
 */

void *peek_list(list_t *list) {
	if (list->head == NULL)
		return NULL;
	return list->head->data;
}

void *peek_back_list(list_t *list) {
	if (list->tail == NULL)
		return NULL;
	return list->tail->data;
}

void *get_list(list_t *list, int loc) {
	struct lnode *n = list->head;
	int i;
	for (i=0; i<loc; i++)
		n = n->next;
	return n->data;
}

/*
 * Removing things from lists
 */

/*
 * Cleaning up.
 */

void destroy_list(list_t *l) {
	struct lnode *n = l->head;
	while (n) {
		struct lnode *tmp = n->next;
		free(n);
		n = tmp;
	}
	free(l);
}

/*
 * Iterators
 *
 * Iterator functionality is relatively complicated. All operations should be
 * with respect to the last value returned (usually by `next', `prev', or
 * `cur').
 */

list_iter_t *list_get_iter(list_t *list) {
	list_iter_t *iter = malloc(sizeof(list_iter_t));
	iter->fake_head = malloc(sizeof(struct lnode));
	iter->fake_head->data = NULL;
	iter->fake_head->next = list->head;
	iter->fake_head->prev = list->tail;
	iter->cur = iter->fake_head;
	return iter;
}

int list_iter_has_next(list_iter_t *iter) {
	return iter->cur->next != NULL;
}

int list_iter_has_prev(list_iter_t *iter) {
	return iter->cur->prev != NULL;
}

void *list_iter_next(list_iter_t *iter) {
	if (iter->cur->next) {
		iter->cur = iter->cur->next;
		return iter->cur->data;
	} else return NULL;
}

void *list_iter_prev(list_iter_t *iter) {
	if (iter->cur->prev) {
		iter->cur = iter->cur->prev;
		return iter->cur->data;
	} else return NULL;
}

void *list_iter_cur(list_iter_t *iter) {
	return iter->cur->data;
}

void list_iter_insert(list_iter_t *iter, void *data) {
	// putting the new data before iter->cur
	struct lnode *nnode = make_lnode(iter->cur, iter->cur->prev, data);
	struct lnode *prev = iter->cur->prev;
	if(prev)
		prev->next = nnode;
	else
		iter->fake_head->next = nnode;
	iter->cur->prev = nnode;
}

void list_iter_add(list_iter_t *iter, void *data) {
	// putting the new data after iter->cur
	struct lnode *nnode = make_lnode(iter->cur->next, iter->cur, data);
	struct lnode *next = iter->cur->next;
	if(next)
		next->prev = nnode;
	else
		iter->fake_head->prev = nnode;
	iter->cur->next = nnode;
	iter->cur = iter->cur->next;
}

void list_iter_destroy(list_iter_t *iter) {
	free(iter->fake_head);
	free(iter);
}

